﻿/*======================================================================
== Copyright : BlueCurve (c)
== Licence   : Gnu/GPL v2.x
== Author    : Teddy Albina
== Email     : bluecurveteam@gmail.com
== Web site  : http://www.codeplex.com/BlueCurve
========================================================================*/
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Transactions;
using Microsoft.Win32.SafeHandles;
using System.Collections.Generic;
using BlueCurve.WebCrawlerRequest;
using System.Runtime.Serialization.Formatters.Binary;

namespace BlueCurve.Common.Transaction
{
    /// <summary>
    /// Permet la lecture et l'écriture de fichiers
    /// en utilisant les transactions ntfs KTM
    /// </summary>
    /// <example>
    /// <![CDATA[
    ///     using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew, new TimeSpan(0,0, 45)))
    ///     {
    ///         Transaction.Current.TransactionCompleted += new TransactionCompletedEventHandler(Current_TransactionCompleted);
    ///         FileGenerationService instanceFile = new FileGenerationService();
    ///         byte[] raw = instanceFile.ReadFile(@"C:\Users\axelle\Desktop\dessin%20technique.pdf");
    ///            
    ///         using (TransactionScope tsDB = new TransactionScope(TransactionScopeOption.RequiresNew))
    ///         {
    ///            instanceFile.Generate(raw, @"C:\Users\axelle\Desktop\T.pdf");
    ///            tsDB.Complete();
    ///         }
    ///         scope.Complete();
    ///     }]]>
    /// </example>
    public class FileGenerationService
    {
        public class FileGeneration
        {
            #region kernel transaction manager api management

            public sealed class SafeTransactionHandle : SafeHandleZeroOrMinusOneIsInvalid
            {
                private SafeTransactionHandle()
                    : base(true)
                {
                }

                public SafeTransactionHandle(IntPtr preexistingHandle, bool ownsHandle)
                    : base(ownsHandle)
                {
                    SetHandle(preexistingHandle);
                }

                public enum FileAccess
                {
                    GENERIC_READ = unchecked((int)0x80000000),
                    GENERIC_WRITE = 0x40000000
                }

                [Flags]
                public enum FileShare
                {
                    FILE_SHARE_NONE = 0x00,
                    FILE_SHARE_READ = 0x01,
                    FILE_SHARE_WRITE = 0x02,
                    FILE_SHARE_DELETE = 0x04
                }

                public enum FileMode
                {
                    CREATE_NEW = 1,
                    CREATE_ALWAYS = 2,
                    OPEN_EXISTING = 3,
                    OPEN_ALWAYS = 4,
                    TRUNCATE_EXISTING = 5
                }

                [DllImport("Kernel32.dll", SetLastError = true)]
                private static extern bool CloseHandle(IntPtr handle);

                override protected bool ReleaseHandle()
                {
                    return CloseHandle(handle);
                }
            }

            /// <summary>
            /// Importation de la fonction native CreateFileTransacted() permettant
            /// de créer une transaction ntfs
            /// </summary>
            /// <returns>SafeFileHandle</returns>
            [DllImport("Kernel32.Dll", EntryPoint = "CreateFileTransacted", CharSet = CharSet.Unicode, SetLastError = true)]
            protected static extern SafeFileHandle CreateFileTransacted(
                   [In] String lpFileName,
                   [In] SafeTransactionHandle.FileAccess dwDesiredAccess,
                   [In] SafeTransactionHandle.FileShare dwShareMode,
                   [In] IntPtr lpSecurityAttributes,
                   [In] SafeTransactionHandle.FileMode dwCreationDisposition,
                   [In] int dwFlagsAndAttributes,
                   [In] IntPtr hTemplateFile,
                   [In] SafeTransactionHandle txHandle,
                   [In] IntPtr miniVersion,
                   [In] IntPtr extendedOpenInformation
               );

            [ComImport]
            [Guid("79427A2B-F895-40e0-BE79-B57DC82ED231")]
            [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            protected interface IKernelTransaction
            {
                void GetHandle(out SafeTransactionHandle ktmHandle);
            }

            #endregion


            #region 'Functions'

            /// <summary>
            /// Ecrit un fichier
            /// </summary>
            /// <param name="raw">Contenu du document sous forme de tableau de byte</param>
            /// <param name="FilePath">Chemin de fichier ou écrire les données</param>
            /// <returns>bool</returns>
            public bool Generate(byte[] raw, string FilePath)
            {
                SafeTransactionHandle txHandle = null;
                SafeFileHandle fileHandle = null;
                bool response = true;

                try
                {
                    if (raw.Length < 1)
                        throw new Exception();

                    IKernelTransaction kernelTx = (IKernelTransaction)TransactionInterop.GetDtcTransaction(System.Transactions.Transaction.Current);
                    kernelTx.GetHandle(out txHandle);

                    fileHandle
                        = CreateFileTransacted(
                            FilePath
                            , SafeTransactionHandle.FileAccess.GENERIC_WRITE
                            , SafeTransactionHandle.FileShare.FILE_SHARE_NONE
                            , IntPtr.Zero
                            , SafeTransactionHandle.FileMode.CREATE_ALWAYS
                            , 0
                            , IntPtr.Zero
                            , txHandle
                            , IntPtr.Zero
                            , IntPtr.Zero);

                    if (fileHandle.IsInvalid)
                    {
                        throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
                    }

                    using (FileStream stream = new FileStream(fileHandle, FileAccess.Write, 1024, false))
                    {
                        BinaryWriter writer = new BinaryWriter(stream, Encoding.UTF8);
                        writer.Write(raw);
                        writer.Close();
                        stream.Close();
                        stream.Dispose();
                    }
                }
                catch
                {
                    System.Transactions.Transaction.Current.Rollback();
                    response = false;
                }
                finally
                {
                    if (fileHandle != null && !fileHandle.IsInvalid)
                    {
                        fileHandle.Close();
                        fileHandle.Dispose();
                    }

                    if (txHandle != null && !txHandle.IsInvalid)
                    {
                        txHandle.Close();
                        txHandle.Dispose();
                    }
                }
                return response;
            }
            
            /// <summary>
            /// Ecrit un fichier
            /// </summary>
            /// <param name="content">Contenu du document</param>
            /// <param name="FilePath">Chemin de fichier ou écrire les données</param>
            /// <returns>bool</returns>
            public virtual bool Generate(string content, string FilePath)
            {
                SafeTransactionHandle txHandle = null;
                SafeFileHandle fileHandle = null;
                bool response = true;

                try
                {
                    IKernelTransaction kernelTx = (IKernelTransaction)TransactionInterop.GetDtcTransaction(System.Transactions.Transaction.Current);
                    kernelTx.GetHandle(out txHandle);

                    fileHandle
                        = CreateFileTransacted(
                            FilePath
                            , SafeTransactionHandle.FileAccess.GENERIC_WRITE
                            , SafeTransactionHandle.FileShare.FILE_SHARE_NONE
                            , IntPtr.Zero
                            , SafeTransactionHandle.FileMode.CREATE_ALWAYS
                            , 0
                            , IntPtr.Zero
                            , txHandle
                            , IntPtr.Zero
                            , IntPtr.Zero);

                    if (fileHandle.IsInvalid)
                    {
                        throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
                    }
                    
                    using (FileStream stream = new FileStream(fileHandle, FileAccess.Write, 1024, false))
                    {
                        StreamWriter writer = new StreamWriter(stream, Encoding.UTF8);
                        writer.Write(content);
                        writer.Close();
                        stream.Close();
                    }
                }
                catch
                {
                    System.Transactions.Transaction.Current.Rollback();
                    response = false;
                }
                finally
                {
                    if (fileHandle != null && !fileHandle.IsInvalid)
                    {
                        fileHandle.Close();
                        fileHandle.Dispose();
                    }

                    if (txHandle != null && !txHandle.IsInvalid)
                    {
                        txHandle.Close();
                        txHandle.Dispose();
                    }
                }
                return response;
            }

            /// <summary>
            /// Ecrit un fichier
            /// </summary>
            /// <param name="content">Contenu du document</param>
            /// <param name="FilePath">Chemin de fichier ou écrire les données</param>
            /// <param name="enc">Encodage dans lequel écrire le fichier</param>
            /// <returns>bool</returns>
            public virtual bool Generate(string content, string FilePath, Encoding enc)
            {
                SafeTransactionHandle txHandle = null;
                SafeFileHandle fileHandle = null;
                bool response = true;

                try
                {
                    IKernelTransaction kernelTx = (IKernelTransaction)TransactionInterop.GetDtcTransaction(System.Transactions.Transaction.Current);
                    kernelTx.GetHandle(out txHandle);

                    fileHandle
                        = CreateFileTransacted(
                            FilePath
                            , SafeTransactionHandle.FileAccess.GENERIC_WRITE
                            , SafeTransactionHandle.FileShare.FILE_SHARE_NONE
                            , IntPtr.Zero
                            , SafeTransactionHandle.FileMode.CREATE_ALWAYS
                            , 0
                            , IntPtr.Zero
                            , txHandle
                            , IntPtr.Zero
                            , IntPtr.Zero);

                    if (fileHandle.IsInvalid)
                    {
                        throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
                    }

                    using (FileStream stream = new FileStream(fileHandle, FileAccess.Write, 1024, false))
                    {
                        StreamWriter writer = new StreamWriter(stream, enc);
                        writer.Write(content);
                        writer.Close();
                        stream.Close();
                    }
                }
                catch
                {
                    System.Transactions.Transaction.Current.Rollback();
                    response = false;
                }
                finally
                {
                    if (fileHandle != null && !fileHandle.IsInvalid)
                    {
                        fileHandle.Close();
                        fileHandle.Dispose();
                    }

                    if (txHandle != null && !txHandle.IsInvalid)
                    {
                        txHandle.Close();
                        txHandle.Dispose();
                    }
                }
                return response;
            }
            
            /// <summary>
            /// Ecrit un fichier ligne par ligne
            /// </summary>
            /// <param name="content">Contenu du document</param>
            /// <param name="FilePath">Chemin de fichier ou écrire les données</param>
            /// <returns>bool</returns>
            public virtual bool Generate(List<string> content, string FilePath)
            {
                SafeTransactionHandle txHandle = null;
                SafeFileHandle fileHandle = null;
                bool response = true;

                try
                {
                    IKernelTransaction kernelTx = (IKernelTransaction)TransactionInterop.GetDtcTransaction(System.Transactions.Transaction.Current);
                    kernelTx.GetHandle(out txHandle);

                    fileHandle
                        = CreateFileTransacted(
                            FilePath
                            , SafeTransactionHandle.FileAccess.GENERIC_WRITE
                            , SafeTransactionHandle.FileShare.FILE_SHARE_NONE
                            , IntPtr.Zero
                            , SafeTransactionHandle.FileMode.CREATE_ALWAYS
                            , 0
                            , IntPtr.Zero
                            , txHandle
                            , IntPtr.Zero
                            , IntPtr.Zero);

                    if (fileHandle.IsInvalid)
                    {
                        throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
                    }

                    using (FileStream stream = new FileStream(fileHandle, FileAccess.Write,  1024, false))
                    {
                        StreamWriter writer = new StreamWriter(stream, Encoding.Unicode);
                        content.ForEach(
                            delegate(string line)
                            {
                                writer.WriteLine(line);
                            });
                        stream.Close();
                        stream.Dispose();
                    }
                }
                catch
                {
                    System.Transactions.Transaction.Current.Rollback();
                    response = false;
                }
                finally
                {
                    if (fileHandle != null && !fileHandle.IsInvalid)
                    {
                        fileHandle.Close();
                        fileHandle.Dispose();
                    }

                    if (txHandle != null && !txHandle.IsInvalid)
                    {
                        txHandle.Close();
                        txHandle.Dispose();
                    }

                    content.Clear();
                }
                return response;
            }
            
            /// <summary>
            /// Ecrit le fichier contentant l'état des données
            /// téléchargé par le robot
            /// </summary>
            /// <param name="content">RequestState</param>
            /// <param name="FilePath">Chemin de fichier ou écrire les données</param>
            /// <returns>bool</returns>
            public virtual bool Generate(RequestState content, string FilePath)
            {
                SafeTransactionHandle txHandle = null;
                SafeFileHandle fileHandle = null;
                bool response = true;

                try
                {
                    IKernelTransaction kernelTx = (IKernelTransaction)TransactionInterop.GetDtcTransaction(System.Transactions.Transaction.Current);
                    kernelTx.GetHandle(out txHandle);

                    fileHandle
                        = CreateFileTransacted(
                            FilePath
                            , SafeTransactionHandle.FileAccess.GENERIC_WRITE
                            , SafeTransactionHandle.FileShare.FILE_SHARE_NONE
                            , IntPtr.Zero
                            , SafeTransactionHandle.FileMode.CREATE_ALWAYS
                            , 0
                            , IntPtr.Zero
                            , txHandle
                            , IntPtr.Zero
                            , IntPtr.Zero);

                    if (fileHandle.IsInvalid)
                    {
                        throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
                    }


                    using (FileStream stream = new FileStream(fileHandle, FileAccess.Write, 1024, false))
                    {
                        BinaryFormatter formatter = new BinaryFormatter();
                        formatter.Serialize(stream, content);
                        stream.Close();
                    }
                }
                catch
                {
                    System.Transactions.Transaction.Current.Rollback();
                    response = false;
                }
                finally
                {
                    if (fileHandle != null && !fileHandle.IsInvalid)
                    {
                        fileHandle.Close();
                        fileHandle.Dispose();
                    }

                    if (txHandle != null && !txHandle.IsInvalid)
                    {
                        txHandle.Close();
                        txHandle.Dispose();
                    }
                }
                return response;
            }


            /// <summary>
            /// Lit un fichier et renvoie
            /// sont contenu sous forme de tableau de byte
            /// </summary>
            /// <param name="FilePath">Chemin d'accès du fichier à lire</param>
            /// <returns>byte[]</returns>
            public byte[] ReadFile(string FilePath)
            {
                SafeTransactionHandle txHandle = null;
                SafeFileHandle fileHandle = null;
                byte[] raw = new byte[0];
                try
                {
                    IKernelTransaction kernelTx = (IKernelTransaction)TransactionInterop.GetDtcTransaction(System.Transactions.Transaction.Current);
                    kernelTx.GetHandle(out txHandle);

                    fileHandle
                        = CreateFileTransacted(
                            FilePath
                            , SafeTransactionHandle.FileAccess.GENERIC_READ
                            , SafeTransactionHandle.FileShare.FILE_SHARE_READ
                            , IntPtr.Zero
                            , SafeTransactionHandle.FileMode.OPEN_ALWAYS
                            , 0
                            , IntPtr.Zero
                            , txHandle
                            , IntPtr.Zero
                            , IntPtr.Zero);

                    if (fileHandle.IsInvalid)
                        throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());

                    using (FileStream stream = new FileStream(fileHandle, FileAccess.Read, 1024, false))
                    {
                        raw = new byte[Convert.ToInt32(stream.Length)];
                        stream.Read(raw, 0, Convert.ToInt32(stream.Length));
                        stream.Close();
                        stream.Dispose();
                    }
                }
                catch
                {
                    raw = new byte[0];
                }
                finally
                {
                    if (fileHandle != null && !fileHandle.IsInvalid)
                    {
                        fileHandle.Close();
                        fileHandle.Dispose();
                    }

                    if (txHandle != null && !txHandle.IsInvalid)
                    {
                        txHandle.Close();
                        txHandle.Dispose();
                    }
                }
                return raw;
            }

            #endregion
        }


        // ===========================================================
        // Usage
        //     using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew, new TimeSpan(0,0, 45)))
        //   {
        //        Transaction.Current.TransactionCompleted += new TransactionCompletedEventHandler(Current_TransactionCompleted);
        //        FileGenerationService instanceFile = new FileGenerationService();
        //        byte[] raw = instanceFile.ReadFile(@"C:\Users\axelle\Desktop\dessin%20technique.pdf");
        //        
        //            using (TransactionScope tsDB = new TransactionScope(TransactionScopeOption.RequiresNew))
        //            {
        //                instanceFile.Generate(raw, @"C:\Users\axelle\Desktop\T.pdf");
        //                tsDB.Complete();
        //            }
        //        scope.Complete();
        //    }
        // ===========================================================


        /// <summary>
        /// Write a file using transaction mode
        /// </summary>
        /// <param name="raw">File content</param>
        /// <param name="path">File location</param>
        /// <returns>bool</returns>
        public bool Generate(byte[] raw, string path)
        {
            FileGeneration fg = new FileGeneration();
            return fg.Generate(raw, path);
        }


        public virtual bool Generate(string content, string path)
        {
            FileGeneration fg = new FileGeneration();
            return fg.Generate(content, path);
        }

        public virtual bool Generate(List<string> Args, string path)
        {
            FileGeneration fg = new FileGeneration();
            return fg.Generate(Args, path);
        }

        public virtual bool Generate(RequestState content, string path)
        {
            FileGeneration fg = new FileGeneration();
            return fg.Generate(content, path);
        }



        /// <summary>
        /// Read a file using transaction mode
        /// </summary>
        /// <param name="path">File location</param>
        /// <returns>byte[] file content</returns>
        public byte[] ReadFile(string path)
        {
            FileGeneration fg = new FileGeneration();
            return fg.ReadFile(path);
        }
    }
}
